package com.ydx.aa76111.agent;

import com.ydx.aa76111.agent.runtime.Agent;
import com.ydx.aa76111.agent.runtime.CoverageTransformer;
import org.jacoco.core.runtime.AgentOptions;
import org.jacoco.core.runtime.IRuntime;
import org.jacoco.core.runtime.InjectedClassRuntime;
import org.jacoco.core.runtime.ModifiedSystemClassRuntime;

import java.lang.instrument.Instrumentation;
import java.util.Collections;
import java.util.Map;
import java.util.Set;

/**
 * javaagent启动入口
 */
public final class PreMain {

    private PreMain() {
        // no instances
    }

    public static void premain(final String options, final Instrumentation inst) throws Exception {
        final AgentOptions agentOptions = new AgentOptions(options);
        final Agent agent = Agent.getInstance();
        final IRuntime runtime = createRuntime(inst);
        runtime.startup(agent.getData());
        inst.addTransformer(new CoverageTransformer(runtime, agentOptions));
    }

    private static IRuntime createRuntime(final Instrumentation inst)
            throws Exception {

        if (redefineJavaBaseModule(inst)) {
            return new InjectedClassRuntime(Object.class, "$JaCoCo");
        }

        return ModifiedSystemClassRuntime.createFor(inst, "java/lang/UnknownError");
    }

    private static boolean redefineJavaBaseModule(
            final Instrumentation instrumentation) throws Exception {
        try {
            Class.forName("java.lang.Module");
        } catch (final ClassNotFoundException e) {
            return false;
        }

        Instrumentation.class.getMethod("redefineModule",
                Class.forName("java.lang.Module"),
                Set.class,
                Map.class,
                Map.class,
                Set.class,
                Map.class
        ).invoke(instrumentation, // instance
                getModule(Object.class), // module
                Collections.emptySet(), // extraReads
                Collections.emptyMap(), // extraExports
                Collections.singletonMap("java.lang",
                        Collections.singleton(
                                getModule(InjectedClassRuntime.class))), // extraOpens
                Collections.emptySet(), // extraUses
                Collections.emptyMap() // extraProvides
        );
        return true;
    }

    private static Object getModule(final Class<?> cls) throws Exception {
        return Class.class
                .getMethod("getModule")
                .invoke(cls);
    }

}
